<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>手机视频流拍照</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
  
  <script>
    tailwind.config = {
      theme: {
        extend: {
          colors: {
            primary: '#165DFF',
            secondary: '#36D399',
            neutral: '#F3F4F6',
          },
          fontFamily: {
            sans: ['Inter', 'system-ui', 'sans-serif'],
          },
        },
      }
    }
  </script>
  
  <style type="text/tailwindcss">
    @layer utilities {
      :root {
        --camera-aspect-ratio: 1/1;
      }
      .content-auto {
        content-visibility: auto;
      }
      .camera-container {
        aspect-ratio: var(--camera-aspect-ratio);
        position: relative;
        overflow: hidden;
      }
      .preview-container {
        aspect-ratio: var(--camera-aspect-ratio);
        position: relative;
      }
      .capture-btn {
        @apply bg-primary hover:bg-primary/90 text-white rounded-full w-16 h-16 flex items-center justify-center transition-all duration-300 active:scale-95 shadow-lg;
      }
      .flash {
        animation: flash 0.3s ease-in-out;
      }
      @keyframes flash {
        0%, 100% { opacity: 1; }
        50% { opacity: 0.5; }
      }
      .scale-in {
        animation: scaleIn 0.3s ease-out;
      }
      @keyframes scaleIn {
        from { transform: scale(0.9); opacity: 0; }
        to { transform: scale(1); opacity: 1; }
      }
    }
  </style>
</head>
<body class="bg-gray-50 min-h-screen font-sans">
  <div class="container mx-auto px-4 py-8 max-w-md">
    <!-- 标题 -->
    <div class="text-center mb-6">
      <h1 class="text-[clamp(1.5rem,3vw,2rem)] font-bold text-gray-800">视频流拍照</h1>
      <p class="text-gray-600 mt-2">通过手机摄像头捕获清晰照片</p>
    </div>
    <!-- 宽高比选择 -->
    <div class="flex justify-center mb-6">
      <div class="inline-flex items-center space-x-2">
      <label for="aspect-ratio" class="text-gray-700 font-medium">宽高比</label>
      <select id="aspect-ratio" class="text-center appearance-none bg-white border-none rounded-full px-3 py-1 text-gray-800 focus:outline-none focus:ring-2 focus:ring-primary transition">
        <option value="16/9">16:9</option>
        <option value="4/3">4:3</option>
        <option value="1/1" selected>1:1</option>
        <option value="3/4">3:4</option>
        <option value="9/16">9:16</option>
      </select>
      </div>
    </div>
    
    <!-- 相机区域 -->
    <div id="camera-section" class="mb-8 scale-in">
      <div class="camera-container bg-gray-200 rounded-xl shadow-lg mb-4">
        <video id="video" class="w-full h-full object-cover" style="transform: scaleX(-1);" autoplay muted playsinline></video>
        <div id="flash" class="absolute inset-0 bg-white opacity-0 pointer-events-none"></div>
      </div>
      
      <div class="text-center mt-2 text-xs text-gray-500" id="video-resolution">
        分辨率：检测中...
      </div>
      
      <div class="flex justify-center space-x-4 mt-6">
        <button id="capture-btn" class="capture-btn">
          <i class="fa fa-camera text-2xl"></i>
        </button>
      </div>
      
      <div class="text-center mt-4 text-sm text-gray-500">
        <p>点击圆形按钮拍照</p>
      </div>
    </div>
    
    <!-- 预览区域 -->
    <div id="preview-section" class="mb-8 hidden scale-in">
      <div class="preview-container bg-gray-200 rounded-xl shadow-lg mb-4">
        <canvas id="canvas" class="w-full h-full object-cover"></canvas>
      </div>
      
      <div class="flex justify-center space-x-4 mt-6">
        <button id="retake-btn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 rounded-full w-16 h-16 flex items-center justify-center transition-all duration-300 active:scale-95 shadow-lg">
          <i class="fa fa-refresh text-2xl"></i>
        </button>
        <button id="download-btn" class="bg-secondary hover:bg-secondary/90 text-white rounded-full w-16 h-16 flex items-center justify-center transition-all duration-300 active:scale-95 shadow-lg">
          <i class="fa fa-download text-2xl"></i>
        </button>
      </div>
    </div>
    
    <!-- 状态栏 -->
    <div id="status" class="fixed bottom-4 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white px-4 py-2 rounded-full text-sm opacity-0 transition-opacity duration-300"></div>
  </div>

  <script>
    // DOM 元素
    const video = document.getElementById('video');
    const canvas = document.getElementById('canvas');
    const captureBtn = document.getElementById('capture-btn');
    const retakeBtn = document.getElementById('retake-btn');
    const downloadBtn = document.getElementById('download-btn');
    const cameraSection = document.getElementById('camera-section');
    const previewSection = document.getElementById('preview-section');
    const flash = document.getElementById('flash');
    const status = document.getElementById('status');
    const aspectRatioSelect = document.getElementById('aspect-ratio');
    const videoResolution = document.getElementById('video-resolution');

    aspectRatioSelect.addEventListener('change', (e) => {
      document.documentElement.style.setProperty('--camera-aspect-ratio', e.target.value);
    });
    
    // 显示状态消息
    function showStatus(message, duration = 2000) {
      status.textContent = message;
      status.classList.remove('opacity-0');
      status.classList.add('opacity-100');
      
      setTimeout(() => {
        status.classList.remove('opacity-100');
        status.classList.add('opacity-0');
      }, duration);
    }
    
    // 初始化摄像头
    async function initCamera() {
      try {
        // 请求摄像头权限并获取视频流
        const stream = await navigator.mediaDevices.getUserMedia({
          video: {
            width: { ideal: 1920 },
            height: { ideal: 1080 },
            facingMode: 'user' // 使用前置摄像头
            // facingMode: 'environment' // 使用后置摄像头
          },
          audio: false
        });
        
        // 将视频流连接到video元素
        video.srcObject = stream;
        
        // 显示状态
        showStatus('摄像头已就绪');
        
        return new Promise((resolve) => {
          video.onloadedmetadata = () => {
            // 调整canvas尺寸与视频匹配
            canvas.width = video.videoWidth;
            canvas.height = video.videoHeight;
            // 更新分辨率显示
            videoResolution.textContent = `分辨率：${video.videoWidth} × ${video.videoHeight}`;
            resolve();
          };
        });
      } catch (err) {
        console.error('获取摄像头失败:', err);
        showStatus('无法访问摄像头，请确保已授予权限');
        videoResolution.textContent = '分辨率：获取失败';
        throw err;
      }
    }
    
    // 捕获照片
    function capturePhoto() {
      const ctx = canvas.getContext('2d');

      // 使用摄像头原始分辨率
      const videoWidth = video.videoWidth;
      const videoHeight = video.videoHeight;

      // 容器的宽高比
      const container = document.querySelector('.camera-container');
      const rect = container.getBoundingClientRect();
      const containerRatio = rect.width / rect.height;
      const videoRatio = videoWidth / videoHeight;

      // 以容器比例裁剪摄像头画面，保证不变形
      let sx, sy, sw, sh;
      if (videoRatio > containerRatio) {
        // 视频比容器宽，左右裁剪
        sh = videoHeight;
        sw = sh * containerRatio;
        sx = (videoWidth - sw) / 2;
        sy = 0;
      } else {
        // 视频比容器窄，上下裁剪
        sw = videoWidth;
        sh = sw / containerRatio;
        sx = 0;
        sy = (videoHeight - sh) / 2;
      }
      // 设置canvas为高分辨率（以视频高度为基准，宽度按容器比例）
      const targetHeight = videoHeight;
      const targetWidth = Math.round(targetHeight * containerRatio);
      canvas.width = targetWidth;
      canvas.height = targetHeight;

      // 闪光灯效果
      flash.classList.add('flash');
      setTimeout(() => {
        flash.classList.remove('flash');
      }, 300);

      // 裁剪并缩放绘制（镜像）
      ctx.save();
      ctx.translate(targetWidth, 0);
      ctx.scale(-1, 1);
      ctx.drawImage(video, sx, sy, sw, sh, 0, 0, targetWidth, targetHeight);
      ctx.restore();

      // 切换到预览模式
      cameraSection.classList.add('hidden');
      previewSection.classList.remove('hidden');

      showStatus('照片已捕获');
    }
    
    // 重新拍照
    function retakePhoto() {
      previewSection.classList.add('hidden');
      cameraSection.classList.remove('hidden');
      showStatus('准备就绪');
    }
    
    // 下载照片
    function downloadPhoto() {
      try {
        // 创建下载链接
        const link = document.createElement('a');
        link.download = `photo-${new Date().toISOString()}.jpg`;
        
        // 将canvas内容转换为JPEG格式的DataURL
        // 使用0.92的质量参数以获得较好的压缩率和质量平衡
        const dataURL = canvas.toDataURL('image/jpeg', 0.92);
        
        // 设置链接的href属性
        link.href = dataURL;
        
        // 模拟点击下载
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        
        showStatus('照片已下载');
      } catch (err) {
        console.error('下载失败:', err);
        showStatus('下载失败，请重试');
      }
    }
    
    // 事件监听
    captureBtn.addEventListener('click', capturePhoto);
    retakeBtn.addEventListener('click', retakePhoto);
    downloadBtn.addEventListener('click', downloadPhoto);
    
    // 页面加载时初始化摄像头
    document.addEventListener('DOMContentLoaded', async () => {
      try {
        await initCamera();
      } catch (err) {
        // 显示错误信息
        showStatus('初始化摄像头失败: ' + err.message, 5000);
      }
    });
    
    // 页面离开时释放摄像头资源
    window.addEventListener('beforeunload', () => {
      if (video.srcObject) {
        video.srcObject.getTracks().forEach(track => track.stop());
      }
    });
  </script>
</body>
</html>
